Skip to content

fix: recording produces correct video duration with real-time ffmpeg encoding#812

Merged
ctate merged 8 commits intovercel-labs:mainfrom
hyunjinee:fix/recording-playwright-style-piped-ffmpeg
Mar 15, 2026
Merged

fix: recording produces correct video duration with real-time ffmpeg encoding#812
ctate merged 8 commits intovercel-labs:mainfrom
hyunjinee:fix/recording-playwright-style-piped-ffmpeg

Conversation

@jin-2-kakaoent
Copy link
Contributor

@jin-2-kakaoent jin-2-kakaoent commented Mar 15, 2026

Closes #811

Summary

  • Spawn ffmpeg at recording start with piped stdin (image2pipe), capture screenshots at a fixed 10fps interval via Page.captureScreenshot, and stream JPEG frames directly to ffmpeg in real-time — no temp files
  • Duration matches wall-clock time regardless of page activity (static or interactive)
  • Switch WebM codec from VP9 to VP8 for correct framerate and wider browser playback support
  • Extract start_recording_task / stop_recording_task helpers on DaemonState to eliminate duplication across 5 handlers
  • Add kill_on_drop(true) to ffmpeg process to prevent zombie on task panic

Test plan

  • Manual test: record start/stop with .webm (VP8) — 10s recording produces 11.0s video
  • Manual test: record start/stop with .mp4 (H.264) — 10s recording produces 11.0s video
  • Static page recording (no interaction) — 5s recording produces 5.1s video
  • Interactive recording (type, search, scroll) — duration matches wall-clock time
  • ffprobe confirms correct codec, framerate (10fps), and duration for both formats

…cording

  Recording previously used Page.captureScreenshot polling at 10fps,
  which was CPU-heavy and produced inconsistent results. Now uses
  Page.startScreencast with throttled acks (35ms interval) to receive
  frames event-driven from Chrome, and pipes JPEG data directly to
  ffmpeg stdin in real-time instead of saving temp files.

  - Spawn ffmpeg at recording start with piped stdin (image2pipe)
  - Background task receives screencast frames, interpolates gaps by
    repeating the last frame based on timestamps, targets 25fps
  - Ack throttling controls Chrome's frame push rate
  - Fix: current frame was never written after the first one
  - Fix: frame count was read before task finished padding
  - Remove tokio-util dependency (replaced CancellationToken with oneshot)
  - Add tokio "process" feature for async child process stdin pipe
  - Extract start/stop_recording_task helpers on DaemonState
  - Add tests for restart, ffmpeg codec selection, and stop without task
@vercel
Copy link
Contributor

vercel bot commented Mar 15, 2026

@hyunjinee is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

  compatibility

  VP9 realtime encoder ignored input framerate, producing 10fps output
  instead of 25fps. This caused inconsistent playback in browsers.
  VP8 respects -framerate 25 and has wider browser playback support.
@ctate
Copy link
Collaborator

ctate commented Mar 15, 2026

Thanks for this @jin-2-kakaoent! The code quality here is solid — the helper extraction, concurrency patterns, and kill_on_drop are all good.

I ran an end-to-end test and hit a problem: a 3-second recording of a static page produced only a 1.04-second video. Chrome's screencast only pushes frames when the screen changes, so for static or low-activity pages you get almost nothing. The old captureScreenshot polling guaranteed a frame every 100ms regardless of page activity — that's important for recordings where duration must match wall-clock time.

Screencast is a great fit for live preview, but for recording I think you need regular frame capture to guarantee correct duration and timing.

…ing duration

  Screencast only pushes frames on visual changes, producing short videos
  on static pages. Screenshot polling captures at a fixed 10fps interval
  regardless of page activity, guaranteeing duration matches wall-clock time.
  ffmpeg piped stdin architecture is preserved — no temp files.
@jin-2-kakaoent
Copy link
Contributor Author

@ctate Thanks for the review!

As you pointed out, screencast only pushes frames on visual changes, so static pages produce short videos.

I've switched the frame source from Page.startScreencast to Page.captureScreenshot polling at 10fps (100ms interval). This guarantees one frame every 100ms regardless of page activity, so duration always matches wall-clock time.

The piped ffmpeg architecture is preserved — screenshots are decoded and written directly to ffmpeg stdin, no temp files.

Test results for 10 seconds video are attached below.

mp4

10s-test.mp4

webm

10s-test.webm

@jin-2-kakaoent jin-2-kakaoent changed the title fix: replace screenshot polling with screencast-based piped ffmpeg recording fix: pipe screenshot frames directly to ffmpeg for reliable recording duration Mar 15, 2026
@jin-2-kakaoent jin-2-kakaoent changed the title fix: pipe screenshot frames directly to ffmpeg for reliable recording duration fix: recording produces correct video duration with real-time ffmpeg encoding Mar 15, 2026
@ctate
Copy link
Collaborator

ctate commented Mar 15, 2026

Works great. Thanks you @jin-2-kakaoent

@ctate ctate merged commit 19ba904 into vercel-labs:main Mar 15, 2026
9 of 11 checks passed
@github-actions github-actions bot mentioned this pull request Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recorded videos output only last less than 1 second

3 participants